package com.hb.config;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jndi.JndiObjectFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import javax.sql.DataSource;

@Configuration
// 使用 Java 配置 Spring Data JPA
// Spring Data JPA 将实现类与接口关联起来是基于接口的名称。但是，Impl 后缀只是默认的做法，如果你想使用其他后缀的话，只需在配置 @EnableJpaRepositories 的时候，设
// 置 repositoryImplementationPostfix 属性即可。这里仍然设置为 “Impl”。
@EnableJpaRepositories(basePackages = "com.hb.repository", repositoryImplementationPostfix = "Impl")
public class JpaConfig {
    /**
     * 使用 LocalEntityManagerFactoryBean 来配置应用程序管理类型的 JPA
     * @return
     */
    @Bean
    public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
        LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean();
        // 赋给 persistenceUnitName 属性的值就是 persistence.xml 中持久化单元的名称
        emfb.setPersistenceUnitName("spitterPU");
        return emfb;
    }

    /**
     * 使用 LocalContainerEntityManagerFactoryBean 来配置容器管理类型的 JPA
     * @param dataSource
     * @param jpaVendorAdapter
     * @return
     */
    @Bean
    // 这里，我们使用了 Spring 配置的数据源来设置 dataSource 属性。任何 javax.sql.DataSource 的实现都是可以的。尽管数据源还可以在 persistence.xml 中进行配置，
    // 但是这个属性指定的数据源具有更高的优先级。jpaVendorAdapter 属性用于指明所使用的是哪一个厂商的 JPA 实现。Spring 提供了多个 JPA 厂商适配器：
    // 1、EclipseLinkJpaVendorAdapter
    // 2、HibernateJpaVendorAdapter
    // 3、OpenJpaVendorAdapter
    // 4、TopLinkJpaVendorAdapter（在 Spring 3.1 版本中，已经将其废弃了）
    public LocalContainerEntityManagerFactoryBean containerEntityManagerFactoryBean(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean cemfb = new LocalContainerEntityManagerFactoryBean();
        cemfb.setDataSource(dataSource);
        cemfb.setJpaVendorAdapter(jpaVendorAdapter);
        // persistence.xml 文件的主要作用就在于识别持久化单元中的实体类。但是从 Spring 3.1 开始，我们能够在 LocalContainerEntityManagerFactoryBean 中
        // 直接设置 packagesToScan 属性。在这个配置中，LocalContainerEntityManagerFactoryBean 会扫描 com.hb.domain 包，查找带有@Entity注解的类。因此，
        // 没有必要在 persistence.xml 文件中进行声明了。同时，因为 DataSource 也是注入到 LocalContainerEntityManagerFactoryBean 中的，所以也没有必要
        // 在 persistence.xml 文件中配置数据库信息了。
        cemfb.setPackagesToScan("com.hb.damain");
        return cemfb;
    }

    /**
     * 配置 JPA 厂商适配器（JPA 实现）
     * @return
     */
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        // 有多个属性需要设置到厂商适配器上，但是最重要的是 database 属性，在上面我们设置了要使用的数据库是 Hypersonic。
        // Hibernate 的 JPA 适配器支持多种数据库，可以通过其 database 属性配置使用哪个数据库：IBM DB2（DB2），Apache Derby（DERBY），H2（H2），Hypersonic（HSQL），
        // Informix（INFORMIX），MySQL（MYSQL），Oracle（ORACLE），PostgresQL（POSTGRESQL），Microsoft SQL Server（SQLSERVER），Sybase（SYBASE）
        adapter.setDatabase(Database.HSQL);
        adapter.setShowSql(true);
        adapter.setGenerateDdl(false);
        adapter.setDatabasePlatform("org.hibernate.dialect.HSQLDialect");
        return adapter;
    }

    /**
     * 在 Java 中配置基于 JNDI 来获取 EntityManagerFactory
     * 尽管这种方法没有返回 EntityManagerFactory，但是它的结果就是一个 EntityManagerFactory bean。这是因为它所返回的 JndiObjectFactoryBean
     * 是 FactoryBean 接口的实现，它能够创建 EntityManagerFactory。原因是 JndiObjectFactoryBean 在应用启动时 Spring 容器确实创建了这个对象 bean，
     * 只不过这时是工厂本身（JndiObjectFactoryBean），然后 Spring 会自动调用工厂 JndiObjectFactoryBean 里的 afterPropertiesSet() 方法去创建我们指定
     * 的 JndiName 所对应的对象 bean，并调用 getObject() 和 getObjectType() 返回已创建好的对象和类型，此时这个 bean 就存在于 Spring 容器中了，往后
     * 就能注入到依赖此对象的地方去。所以这里配置 JndiObjectFactoryBean 后就相当于配置了基于 JNDI 的 EntityManagerFactory。
     * @return
     */
    @Bean
    public JndiObjectFactoryBean jndiObjectFactoryBean() {
        JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
        jndiObjectFB.setJndiName("jdbc/SpitterDS");
        return jndiObjectFB;
    }

    /**
     * 另外，还需要了解 @PersistenceUnit 和 @PersistenceContext 并不是 Spring 的注解，它们是由 JPA 规范提供的。为了让 Spring 理解这些注解，
     * 并注入 EntityManagerFactory 或 EntityManager，我们必须要配置 Spring 的 PersistenceAnnotationBeanPostProcessor。如果你已经使用了
     * <context:annotation-config> 或 <context:componentscan>，那么你就不必再担心了，因为这些配置元素会自动注册PersistenceAnnotationBeanPostProcessorbean。
     * 否则的话，我们需要显式地注册这个 bean。
     * @return
     */
    @Bean
    public PersistenceAnnotationBeanPostProcessor paPostProcessor() {
        return new PersistenceAnnotationBeanPostProcessor();
    }

    /**
     * 由于在基于 JPA 的 repository 实现中使用了 @Repository 注解，我们需要配置异常转换功能的 PersistenceExceptionTranslationPostProcessor 类型 bean。
     * @return
     */
    @Bean
    public BeanPostProcessor persistenceTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}
